/***************************************************************************
 *            fb2parser.c
 *
 *  Sun Aug 13 13:03:57 2006
 *  Copyright  2006  Alexander P Telenga
 *  telenga@gmail.com
 ****************************************************************************/

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

#include "include/fb2parser.h"


static void parseP (xmlDocPtr doc, xmlNodePtr cur, int add_tab,
		    FB2Content * fb);
static void parseAuthor (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseAnnotation (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseLink (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseStanza (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseSection (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseTitleInfo (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseTitle (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseDescription (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parsePoem (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseCite (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseStyle (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseEpigraph (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseBody (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseBinary (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);
static void parseImage (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb);

static void bufferAppend (char *data, int len, FB2Content * fb);
static void addMark (int start, int end, int type, char *id, FB2Content * fb);

/*static int removeSpaces (char *buffer, int length);*/

static FB2Mark *getLink (xmlNodePtr cur, FB2Content * fb);


/**********************************************************************/

static const char utf8_skip_data[256] = {
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1, 1, 1, 1, 1,
	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
		2, 2, 2, 2, 2, 2, 2, 2, 2,
	3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4,
		4, 5, 5, 5, 5, 6, 6, 1, 1
};

static int
utf8_strlen (const char *p)
{
	int len = 0;

	while (*p)
	{
		p = p + utf8_skip_data[*(unsigned char *) p];
		++len;
	}

	return len;
}


static void
bufferAppend (char *data, int len, FB2Content * fb)	/*, int remove_spaces) */
{
	char **buffer;
	size_t *buffer_size;
	int *current_index;
	/*int new_len; */

	if (!data)
		return;

	if (fb->current_buffer == DESCRIPTION_BUFFER)
	{
		buffer = &(fb->description);
		buffer_size = &(fb->description_buffer_size);
		current_index = &(fb->description_current_index);
	}
	else if (fb->current_buffer == TEXT_BUFFER)
	{
		buffer = &(fb->text);
		buffer_size = &(fb->text_buffer_size);
		current_index = &(fb->text_current_index);
	}
	else if (fb->current_buffer == BINARY_BUFFER)
	{
		buffer = &(fb->binaries[fb->num_binaries]->buffer);
		buffer_size = &(fb->binaries[fb->num_binaries]->buffer_size);
		current_index =
			&(fb->binaries[fb->num_binaries]->current_index);
	}

	/* remove spaces */
/*
  if (remove_spaces) {
    new_len = removeSpaces(data, len);
    len = new_len;
  }
*/
	/* realloc buffer */
	if (*current_index + len >= *buffer_size)
	{
		while (1)
		{
			*buffer_size = *buffer_size * 2;
			*buffer = (char *) realloc (*buffer, *buffer_size);
			if (*current_index + len < *buffer_size)
			{
				break;
			}
		}
	}

	strncpy ((*buffer) + *current_index, data, len);
	*current_index += len;

	if (fb->current_buffer == TEXT_BUFFER)
	{
		int u_len;
		u_len = utf8_strlen (data);
		fb->utf8_current_index += u_len;
	}

	return;
}

static void
addMark (int start, int end, int type, char *id, FB2Content * fb)
{
	FB2Mark *m;
	int i;

	if (fb->current_buffer == TEXT_BUFFER)
	{
		for (i = 0; i < BUF_SIZE - 1; i++)
		{
			if (fb->marks[i] == NULL)
			{
				m = (FB2Mark *) malloc (sizeof (FB2Mark));
				m->start = start;
				m->end = end;
				m->type = type;
				if (id)
					strncpy (m->id, id, BUF_SIZE);
				fb->marks[i] = m;
				fb->marks[i + 1] = NULL;
				fb->num_marks++;
				break;
			}
		}
	}
	return;
}


/*
static int
removeSpaces (char *buffer, int length)
{
  int i, new_length;
  i = 0;
  new_length = length;
  while (i < new_length) {

    if (isspace(buffer[i])) {
      int j;
      buffer[i] = ' ';
      for (j = i+1; j < new_length; j++)
	if (!isspace(buffer[j])) break;
      if (i+1 != j) {
	memmove(buffer+i+1, buffer+j, length-j);
	new_length -= j-(i+1);
	i = j;
      }
    }
    i++;
  }
  return new_length;
}
*/


static FB2Mark *
getLink (xmlNodePtr cur, FB2Content * fb)
{
	FB2Mark *mark = NULL;
	xmlChar *prop;

	/* id */
	prop = xmlGetProp (cur, (const xmlChar *) "id");
	if (prop)
	{
		int i;

		for (i = 0; i < BUF_SIZE - 1; i++)
		{
			FB2Mark *m;

			m = fb->marks[i];
			if (m == NULL)
			{
				break;
			}
			if (m->type == LINK_TYPE || m->type == NOTE_TYPE)
			{

				if (!xmlStrcmp
				    ((const xmlChar *) m->id + 1, prop))
				{
					mark = m;
					mark->link_start =
						fb->utf8_current_index;
				}
			}
		}

		xmlFree (prop);

	}
	return mark;
}


static void
parseAuthor (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{

	xmlChar *content;
	cur = cur->children;
	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "first-name")
		    || !xmlStrcmp (cur->name, (const xmlChar *) "middle-name")
		    || !xmlStrcmp (cur->name, (const xmlChar *) "last-name")
		    || !xmlStrcmp (cur->name, (const xmlChar *) "nickname"))
		{

			content = xmlNodeGetContent (cur->children);
			if (content)
			{
				int len = strlen (fb->author);
				if (len > 0)
				{
					strncat (fb->author, " ",
						 BUF_SIZE - len);
					len++;
				}
				strncat (fb->author, content, BUF_SIZE - len);
			}

			xmlFree (content);
		}

		cur = cur->next;
	}
	return;
}


static void
parseAnnotation (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	cur = cur->children;

	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "p"))
		{
			parseP (doc, cur, 1, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "empty-line"))
		{
			bufferAppend ("\n", 1, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "poem"))
		{
			parsePoem (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "cite"))
		{
			parseCite (doc, cur, fb);

		}

		cur = cur->next;
	}
	return;
}


static void
parseTitleInfo (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{

	xmlChar *content;
	cur = cur->children;
	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "p"))
		{
			parseP (doc, cur, 1, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "empty-line"))
		{
			bufferAppend ("\n", 1, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "genre"))
		{
			content = xmlNodeGetContent (cur->children);

			if (content)
			{
				/* add genre */
				int i;
				for (i = 0; i < BUF_SIZE - 1; i++)
				{
					if (fb->genres[i] == NULL)
					{
						fb->genres[i] = content;
						fb->genres[i + 1] = NULL;
						fb->num_genres++;
						break;
					}
				}
			}

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "book-title"))
		{
			content = xmlNodeGetContent (cur->children);
			if (content)
				strncpy (fb->name, content, BUF_SIZE);
			xmlFree (content);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "author"))
		{
			parseAuthor (doc, cur, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "annotation"))
		{
			parseAnnotation (doc, cur, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "coverpage"))
		{
			xmlNodePtr c;
			c = cur->children;
			while (c)
			{
				if (!xmlStrcmp
				    (c->name, (const xmlChar *) "image"))
				{
					xmlChar *cover_href;
					cover_href =
						xmlGetProp (c,
							    (const xmlChar *)
							    "href");
					strncpy (fb->cover_href, cover_href,
						 BUF_SIZE);
					xmlFree (cover_href);
					break;
				}
				c = c->next;
			}
			/*
			 * content = xmlNodeGetContent(cur->children);
			 * if (content)
			 * strncpy(fb->cover_href, content, BUF_SIZE);
			 */
			//if (cover_href)
			/*xmlFree(content); */

		}

		cur = cur->next;
	}
	return;
}


static void
parseTitle (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	int start;

	bufferAppend ("\n", 1, fb);

	start = fb->utf8_current_index;

	cur = cur->children;

	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "p"))
		{
			parseP (doc, cur, 0, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "empty-line"))
		{
			bufferAppend ("\n", 1, fb);

		}
		cur = cur->next;
	}

	addMark (start, fb->utf8_current_index - 1, BOOKMARK_TYPE, NULL, fb);

	bufferAppend ("\n", 1, fb);

	return;
}


static void
parseDescription (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	cur = cur->children;
	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "title-info"))
		{
			parseTitleInfo (doc, cur, fb);

		}
		cur = cur->next;
	}
	return;
}


static void
parseEpigraph (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	xmlChar *content;

	cur = cur->children;
	while (cur != NULL)
	{

		if (xmlNodeIsText (cur))
		{
			/*
			 * content = xmlNodeGetContent(cur);
			 * bufferAppend(content, xmlStrlen(content), fb);
			 * xmlFree(content);
			 */
		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "cite"))
		{
			parseCite (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "poem"))
		{
			parsePoem (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "p"))
		{
			parseP (doc, cur, 1, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "empty-line"))
		{
			bufferAppend ("\n", 1, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "text-author"))
		{

			content = xmlNodeGetContent (cur->children);
			if (content)
			{
				bufferAppend ("\t", 1, fb);
				bufferAppend (content, xmlStrlen (content),
					      fb);
				bufferAppend ("\n", 1, fb);
			}
			xmlFree (content);

		}
		cur = cur->next;
	}
	return;
}


static void
parseP (xmlDocPtr doc, xmlNodePtr cur, int add_tab, FB2Content * fb)
{
	xmlChar *content;
	FB2Mark *mark;

	mark = getLink (cur, fb);

	cur = cur->children;

	if (add_tab)
		bufferAppend ("\t", 1, fb);

	while (cur != NULL)
	{

		if (xmlNodeIsText (cur))
		{
			content = xmlNodeGetContent (cur);
			if (content)
			{
				/*
				 * int len;
				 * len = removeSpaces(content, xmlStrlen(content));
				 * bufferAppend(content, len, fb);
				 */
				bufferAppend (content, xmlStrlen (content),
					      fb);
			}
			xmlFree (content);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "style"))
		{
			parseStyle (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "strong"))
		{
			content = xmlNodeGetContent (cur->children);
			if (content)
			{
				int start = fb->utf8_current_index;
				bufferAppend (content, xmlStrlen (content),
					      fb);
				addMark (start, fb->utf8_current_index,
					 STRONG_TYPE, NULL, fb);
			}
			xmlFree (content);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "emphasis"))
		{
			content = xmlNodeGetContent (cur->children);
			if (content)
			{
				int start = fb->utf8_current_index;
				bufferAppend (content, xmlStrlen (content),
					      fb);
				addMark (start, fb->utf8_current_index,
					 EMPHASIS_TYPE, NULL, fb);
			}
			xmlFree (content);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "a"))
		{
			parseLink (doc, cur, fb);

		}

		cur = cur->next;
	}

	bufferAppend ("\n", 1, fb);

	if (mark)
		mark->link_end = fb->text_current_index;

	return;
}


static void
parseLink (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	xmlChar *content;
	xmlChar *href_prop, *note_prop;
	int start = fb->utf8_current_index;

	/* id */
	href_prop = xmlGetProp (cur, (const xmlChar *) "href");
	/* type="note" */
	note_prop = xmlGetProp (cur, (const xmlChar *) "type");
	if (note_prop && (!xmlStrcmp (note_prop, (const xmlChar *) "note")))
		bufferAppend ("[", 1, fb);

	cur = cur->children;

	/*bufferAppend("[", 1, fb); */

	while (cur != NULL)
	{

		if (xmlNodeIsText (cur))
		{
			content = xmlNodeGetContent (cur);
			if (content)
				bufferAppend (content, xmlStrlen (content),
					      fb);
			xmlFree (content);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "style"))
		{
			parseStyle (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "strong"))
		{
			content = xmlNodeGetContent (cur->children);
			if (content)
			{
				int st = fb->utf8_current_index;
				bufferAppend (content, xmlStrlen (content),
					      fb);
				addMark (st, fb->utf8_current_index,
					 STRONG_TYPE, NULL, fb);
			}
			xmlFree (content);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "emphasis"))
		{
			content = xmlNodeGetContent (cur->children);
			if (content)
			{
				int st = fb->utf8_current_index;
				bufferAppend (content, xmlStrlen (content),
					      fb);
				addMark (st, fb->utf8_current_index,
					 EMPHASIS_TYPE, NULL, fb);
			}
			xmlFree (content);

		}

		cur = cur->next;
	}

	if (href_prop)
	{
		if (note_prop
		    && (!xmlStrcmp (note_prop, (const xmlChar *) "note")))
		{
			bufferAppend ("]", 1, fb);
			addMark (start, fb->utf8_current_index, NOTE_TYPE,
				 href_prop, fb);
			xmlFree (note_prop);
		}
		else
		{
			addMark (start, fb->utf8_current_index, LINK_TYPE,
				 href_prop, fb);
		}
		xmlFree (href_prop);
	}

	return;
}


static void
parsePoem (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	cur = cur->children;
	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "title"))
		{
			parseTitle (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "epigraph"))
		{
			parseEpigraph (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "p"))
		{
			parseP (doc, cur, 1, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "empty-line"))
		{
			bufferAppend ("\n", 1, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "stanza"))
		{
			bufferAppend ("\n", 1, fb);
			parseStanza (doc, cur, fb);
		}

		cur = cur->next;
	}
	bufferAppend ("\n", 1, fb);
	return;
}


static void
parseStanza (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	/*xmlChar *content; */
	cur = cur->children;

	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "v"))
		{
			parseP (doc, cur, 1, fb);
			/*
			 * content = xmlNodeGetContent(cur->children);
			 * if (content) {
			 * bufferAppend("\t", 1, fb);
			 * bufferAppend(content, xmlStrlen(content), fb);
			 * bufferAppend("\n", 1, fb);
			 * }
			 * xmlFree(content);
			 */
		}
		cur = cur->next;
	}
	return;
}


static void
parseCite (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	xmlChar *content;
	cur = cur->children;

	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "poem"))
		{
			parsePoem (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "p"))
		{
			parseP (doc, cur, 1, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "empty-line"))
		{
			bufferAppend ("\n", 1, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "text-author"))
		{
			content = xmlNodeGetContent (cur->children);
			if (content)
			{
				bufferAppend ("\t", 1, fb);
				bufferAppend (content, xmlStrlen (content),
					      fb);
				bufferAppend ("\n", 1, fb);
			}
			xmlFree (content);

		}

		cur = cur->next;
	}
	return;
}


static void
parseStyle (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	xmlChar *content;
	xmlChar *prop;
	int start = fb->utf8_current_index;

	prop = xmlGetProp (cur, (const xmlChar *) "name");

	cur = cur->children;

	while (cur != NULL)
	{

		if (xmlNodeIsText (cur))
		{
			content = xmlNodeGetContent (cur);
			if (content)
				bufferAppend (content, xmlStrlen (content),
					      fb);
			xmlFree (content);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "style"))
		{
			parseStyle (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "strong"))
		{
			content = xmlNodeGetContent (cur->children);
			if (content)
			{
				int st = fb->utf8_current_index;
				bufferAppend (content, xmlStrlen (content),
					      fb);
				addMark (st, fb->utf8_current_index,
					 STRONG_TYPE, NULL, fb);
			}
			xmlFree (content);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "emphasis"))
		{
			content = xmlNodeGetContent (cur->children);
			if (content)
			{
				int st = fb->utf8_current_index;
				bufferAppend (content, xmlStrlen (content),
					      fb);
				addMark (st, fb->utf8_current_index,
					 EMPHASIS_TYPE, NULL, fb);
			}
			xmlFree (content);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "a"))
		{
			parseLink (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "image"))
		{
			parseImage (doc, cur, fb);

		}

		cur = cur->next;
	}

	if (prop && (!xmlStrcmp (prop, (const xmlChar *) "italic")))
	{
		addMark (start, fb->utf8_current_index, EMPHASIS_TYPE, NULL,
			 fb);
		xmlFree (prop);
	}

	return;
}

static void
parseImage (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	xmlChar *prop;

	prop = xmlGetProp (cur, (const xmlChar *) "href");
	if (prop)
	{

		bufferAppend ("\n\n", 2, fb);

		addMark (fb->utf8_current_index - 1, fb->utf8_current_index,
			 IMAGE_TYPE, prop, fb);

	}
	xmlFree (prop);

	return;
}


static void
parseSection (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	FB2Mark *mark;

	mark = getLink (cur, fb);

	cur = cur->children;
	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "title"))
		{
			parseTitle (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "section"))
		{
			parseSection (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "poem"))
		{
			parsePoem (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "cite"))
		{
			parseCite (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "epigraph"))
		{
			parseEpigraph (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "p"))
		{
			parseP (doc, cur, 1, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "subtitle"))
		{
			int start = fb->utf8_current_index;
			parseP (doc, cur, 0, fb);
			addMark (start, fb->utf8_current_index, BOOKMARK_TYPE,
				 NULL, fb);

		}
		else if (!xmlStrcmp
			 (cur->name, (const xmlChar *) "empty-line"))
		{
			bufferAppend ("\n", 1, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "image"))
		{
			parseImage (doc, cur, fb);
		}

		cur = cur->next;
	}

	if (mark)
		mark->link_end = fb->text_current_index;

	return;
}


static void
parseBody (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{
	xmlChar *prop;

	prop = xmlGetProp (cur, (const xmlChar *) "name");
	if (prop)
	{
		int start = fb->utf8_current_index;
		bufferAppend ("\n\n", 2, fb);
		bufferAppend (prop, xmlStrlen (prop), fb);
		bufferAppend ("\n\n", 2, fb);
		addMark (start, fb->utf8_current_index, BOOKMARK_TYPE, NULL,
			 fb);
		xmlFree (prop);
	}


	cur = cur->children;
	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "section"))
		{
			parseSection (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "title"))
		{
			parseTitle (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "image"))
		{
			parseImage (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "epigraph"))
		{
			parseEpigraph (doc, cur, fb);

		}


		cur = cur->next;
	}
	return;
}


static void
parseBinary (xmlDocPtr doc, xmlNodePtr cur, FB2Content * fb)
{

	xmlChar *content;
	xmlChar *prop;

	if (fb->num_binaries == BUF_SIZE - 1)
		return;

	/* initialize new binary buffer */
	fb->binaries[fb->num_binaries] =
		(FB2Binary *) malloc (sizeof (FB2Binary));
	fb->binaries[fb->num_binaries]->buffer = (char *) malloc (BUF_SIZE);
	fb->binaries[fb->num_binaries]->buffer_size = BUF_SIZE;
	fb->binaries[fb->num_binaries + 1] = NULL;
	fb->binaries[fb->num_binaries]->current_index = 0;
	fb->binaries[fb->num_binaries]->id[0] = '\0';
	fb->binaries[fb->num_binaries]->content_type[0] = '\0';

	/* id */
	prop = xmlGetProp (cur, (const xmlChar *) "id");
	if (prop)
		strncpy (fb->binaries[fb->num_binaries]->id, prop, BUF_SIZE);
	xmlFree (prop);

	/* content-type */
	prop = xmlGetProp (cur, (const xmlChar *) "content-type");
	if (prop)
		strncpy (fb->binaries[fb->num_binaries]->content_type, prop,
			 BUF_SIZE);
	xmlFree (prop);

	cur = cur->children;

	while (cur != NULL)
	{

		if (xmlNodeIsText (cur))
		{
			content = xmlNodeGetContent (cur);

			if (content)
			{
				bufferAppend (content, xmlStrlen (content),
					      fb);
			}
			xmlFree (content);
		}

		cur = cur->next;
	}

	bufferAppend ("\0", 1, fb);

	fb->num_binaries++;

	return;
}


FB2Content *
parseFile (char *filename)
{

	xmlDocPtr doc;
	xmlNodePtr cur;
	FB2Content *fb;

	doc = xmlParseFile (filename);

	if (doc == NULL)
	{
		fprintf (stderr, "Document not parsed successfully. \n");
		return NULL;
	}

	cur = xmlDocGetRootElement (doc);

	if (cur == NULL)
	{
		fprintf (stderr, "empty document\n");
		xmlFreeDoc (doc);
		return NULL;
	}

	if (xmlStrcmp (cur->name, (const xmlChar *) "FictionBook"))
	{
		fprintf (stderr,
			 "document of the wrong type, root node != FictionBook\n");
		xmlFreeDoc (doc);
		return NULL;
	}

	fb = (FB2Content *) malloc (sizeof (FB2Content));

	/* initialize buffers */
	fb->text = (char *) malloc (BUF_SIZE);
	fb->text_buffer_size = BUF_SIZE;
	fb->text_current_index = 0;
	fb->utf8_current_index = 0;
	fb->description = (char *) malloc (BUF_SIZE);
	fb->description_buffer_size = BUF_SIZE;
	fb->description_current_index = 0;

	*fb->author = '\0';
	*fb->name = '\0';
	*fb->cover_href = '\0';

	fb->genres[0] = NULL;
	fb->num_genres = 0;

	fb->marks[0] = NULL;
	fb->num_marks = 0;

	fb->binaries[0] = NULL;
	fb->num_binaries = 0;

	//fb->links[0] = NULL;
	//fb->num_links = 0;

	cur = cur->children;
	while (cur != NULL)
	{

		if (!xmlStrcmp (cur->name, (const xmlChar *) "description"))
		{
			fb->current_buffer = DESCRIPTION_BUFFER;
			parseDescription (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "body"))
		{
			fb->current_buffer = TEXT_BUFFER;
			parseBody (doc, cur, fb);

		}
		else if (!xmlStrcmp (cur->name, (const xmlChar *) "binary"))
		{
			fb->current_buffer = BINARY_BUFFER;
			parseBinary (doc, cur, fb);

		}

		cur = cur->next;
	}
	xmlFreeDoc (doc);

	fb->current_buffer = DESCRIPTION_BUFFER;
	bufferAppend ("\0", 1, fb);

	fb->current_buffer = TEXT_BUFFER;
	bufferAppend ("\0", 1, fb);

	return fb;
}

void
freeFB2 (FB2Content * fb)
{
	int i;

	for (i = 0; i < BUF_SIZE - 1; i++)
	{
		if (fb->genres[i] == NULL)
			break;
		xmlFree (fb->genres[i]);
	}

	for (i = 0; i < BUF_SIZE - 1; i++)
	{
		FB2Mark *m = fb->marks[i];
		if (m == NULL)
			break;
		free (m);
	}

	for (i = 0; i < BUF_SIZE - 1; i++)
	{
		FB2Binary *bin = fb->binaries[i];
		if (bin == NULL)
			break;
		free (bin->buffer);
		free (bin);
	}

	free (fb->description);
	free (fb->text);

	return;
}
